//
//  SleepBarRoundrender.swift
//  DaRings
//
//  Created by 尹琼 on 2022/11/1.
//

import Foundation
import UIKit
import Charts

@objc public protocol RoundBarchartRendererDrawDelegate {
    
    @objc optional
    func drawBarShadow(context: CGContext, barRects:[CGRect], barRect: CGRect, index: Int, singleColor: Bool)
    
    @objc optional
    func drawBar(context: CGContext, barRects:[CGRect], barRect: CGRect, index: Int, color: UIColor, isSingleColor: Bool)
    
    @objc optional
    func drawHighlighted(context: CGContext, barRect: CGRect)
    
}

extension RoundBarchartRenderer: RoundBarchartRendererDrawDelegate {
    
    public func drawBarShadow(context: CGContext, barRects:[CGRect], barRect: CGRect, index: Int, singleColor: Bool) {
        
        if rectCorner == .none { // 没有圆角
            context.fill(barRect)
            return
        }
        
        
        var uiRectCorner: UIRectCorner = .allCorners
        
        let hasHeightRects = barRects.filter { $0.size.height != 0 }
        
        // 一个bar 只有一个显示时候
        if hasHeightRects.count == 1 || singleColor || rectCorner == .eachEntryTopAndBottom {
            
            let size = barRect.size
            
            switch rectCorner {
                
            case .none:
                break
            case .top:
                uiRectCorner = [.topLeft, .topRight]
                
    
                break
            case .bottom:
                uiRectCorner = [.bottomLeft, .bottomRight]
                
            case .topAndBottom:
                break
                
            case .eachEntryTopAndBottom:
                break
            }
            
            
            var cornerRadius = size.height > size.width ? size.width/2.0 : size.height/2.0
            if let radiuDirectionAtMin = self.radiuDirectionAtMin {
                
                switch radiuDirectionAtMin {
                case .width:
                    cornerRadius = size.width
                case .height:
                    cornerRadius = size.height
                }
                
            }
            let path = UIBezierPath(roundedRect: barRect, byRoundingCorners: uiRectCorner, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
            path.fill()
            return
        }
        
        
        let isFirst = hasHeightRects.first == barRect
        // 每一列 第一个
        if isFirst == true {
            
            uiRectCorner = (rectCorner == .topAndBottom || rectCorner == .top) ? [.bottomLeft, .bottomRight] : []
            let size = barRect.size
            var cornerRadius = size.height > size.width ? size.width/2.0 : size.height/2.0
            
            if let radiuDirectionAtMin = self.radiuDirectionAtMin {
                
                switch radiuDirectionAtMin {
                case .width:
                    
                    cornerRadius = size.width/2.0
                case .height:
                    cornerRadius = size.height/2.0
                }
                
            }
            
            let path = UIBezierPath(roundedRect: barRect, byRoundingCorners: uiRectCorner, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
            path.fill()
           
            return
        }
        
        let isLast = hasHeightRects.last == barRect
         
         // 每一列 最后一个
         if isLast == true {
             
             uiRectCorner = (rectCorner == .topAndBottom || rectCorner == .bottom) ? [.topLeft, .topRight] : []
             let size = barRect.size
             var cornerRadius = size.height > size.width ? size.width/2.0 : size.height/2.0
             
             if let radiuDirectionAtMin = self.radiuDirectionAtMin {
                 
                 switch radiuDirectionAtMin {
                 case .width:
                     
                     cornerRadius = size.width/2.0
                 case .height:
                     cornerRadius = size.height/2.0
                 }
                 
             }
             let path = UIBezierPath(roundedRect: barRect, byRoundingCorners: uiRectCorner, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
             path.fill()
            
             return
             
         }
       
        context.fill(barRect)
        
        
    }
    
    public func drawBar(context: CGContext, barRects:[CGRect], barRect: CGRect, index: Int, color: UIColor, isSingleColor: Bool) {
       
        if rectCorner == .none { // 没有圆角
            context.fill(barRect)
            return
        }
        
        var uiRectCorner: UIRectCorner = .allCorners
        
        let hasHeightRects = barRects.filter { $0.size.height != 0 }
        
        // 一个bar 只有一个显示时候
        if (hasHeightRects.count == 1 || isSingleColor || rectCorner == .eachEntryTopAndBottom) {
            
            switch rectCorner {
                
            case .none:
                break
            case .top:
                uiRectCorner = [.topLeft, .topRight]
                break
            case .bottom:
                uiRectCorner = [.bottomLeft, .bottomRight]
            case .topAndBottom, .eachEntryTopAndBottom:
                break
                
            }
            
            let size = barRect.size
            let cornerRadius = size.height > size.width ? size.width/2.0 : size.height/2.0
            let path = UIBezierPath(roundedRect: barRect, byRoundingCorners: uiRectCorner, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
            path.fill()
            return
        }
        
        let isFirst = hasHeightRects.first == barRect
        // 每一列 第一个
        if isFirst == true {
            
            uiRectCorner = (rectCorner == .topAndBottom || rectCorner == .top) ? [.bottomLeft, .bottomRight] : []
            let size = barRect.size
            let cornerRadius = size.width/2.0
            let path = UIBezierPath(roundedRect: barRect, byRoundingCorners: uiRectCorner, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
            path.fill()
           
            return
        }
        
        let isLast = hasHeightRects.last == barRect
         
         // 每一列 最后一个
        if isLast == true {
             
             uiRectCorner = (rectCorner == .topAndBottom || rectCorner == .bottom) ? [.topLeft, .topRight] : []
             let size = barRect.size
             let cornerRadius = size.width/2.0
             let path = UIBezierPath(roundedRect: barRect, byRoundingCorners: uiRectCorner, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
             path.fill()
            
             return
             
         }
       
        context.fill(barRect)
        
 
    }
    
    public func drawHighlighted(context: CGContext, barRect: CGRect) {
        print("hight bar = \(barRect)")
        
        if rectCorner == .none { // 没有圆角
            context.fill(barRect)
            return
        }
        
        var uiRectCorner: UIRectCorner = .allCorners
        
            switch rectCorner {
                
            case .none:
                break
            case .top:
                uiRectCorner = [.topLeft, .topRight]
                break
            case .bottom:
                uiRectCorner = [.bottomLeft, .bottomRight]
            case .topAndBottom, .eachEntryTopAndBottom:
                break
            }
            
            let size = barRect.size
            let cornerRadius = size.height > size.width ? size.width/2.0 : size.height/2.0
            let path = UIBezierPath(roundedRect: barRect, byRoundingCorners: uiRectCorner, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
            path.fill()
            return
        
    }
    
}

open class RoundBarchartRenderer: BarChartRenderer {
    
    /// 圆角倒角类型
    public var rectCorner: RoundRectCorner = .topAndBottom
    
    public weak var delegate: RoundBarchartRendererDrawDelegate?
    
    /// 极小值时，倒角方向
    var radiuDirectionAtMin: RadiuDirection?
    
    
     // [CGRect] per dataset
    private var _buffers = [Buffer]()
    
    private var _barShadowRectBuffer: CGRect = CGRect()
    
    private lazy var accessibilityOrderedElements: [[NSUIAccessibilityElement]] = accessibilityCreateEmptyOrderedElements()
    
    public override init(dataProvider: BarChartDataProvider, animator: Animator, viewPortHandler: ViewPortHandler) {
        super.init(dataProvider: dataProvider, animator: animator, viewPortHandler: viewPortHandler)
        
        delegate = self
        
    }
    
    
    
    open override func initBuffers()
    {
        super.initBuffers()
        
        
        if let barData = dataProvider?.barData
        {
            // Matche buffers count to dataset count
            if _buffers.count != barData.dataSetCount
            {
                while _buffers.count < barData.dataSetCount
                {
                    _buffers.append(Buffer())
                }
                while _buffers.count > barData.dataSetCount
                {
                    _buffers.removeLast()
                }
            }
            
            for i in stride(from: 0, to: barData.dataSetCount, by: 1)
            {
                let set = barData.dataSets[i] as! IBarChartDataSet
                let size = set.entryCount * (set.isStacked ? set.stackSize : 1)
                if _buffers[i].rects.count != size
                {
                    _buffers[i].rects = [CGRect](repeating: CGRect(), count: size)
                }
            }
        }
        else
        {
            _buffers.removeAll()
        }
    }
    
    

    open override func drawDataSet(context: CGContext, dataSet: IBarChartDataSet, index: Int) {
        guard let dataProvider = dataProvider else { return }

        let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency)

        prepareBuffer(dataSet: dataSet, index: index)
        trans.rectValuesToPixel(&_buffers[index].rects)

        let borderWidth = dataSet.barBorderWidth
        let borderColor = dataSet.barBorderColor
        let drawBorder = borderWidth > 0.0
        
        context.saveGState()
        
        // draw the bar shadow before the values
        if dataProvider.isDrawBarShadowEnabled
        {
            guard let barData = dataProvider.barData else { return }
            
            let barWidth = barData.barWidth
            let barWidthHalf = barWidth / 2.0
            var x: Double = 0.0
            
            for i in stride(from: 0, to: min(Int(ceil(Double(dataSet.entryCount) * animator.phaseX)), dataSet.entryCount), by: 1)
            {
                guard let e = dataSet.entryForIndex(i) as? BarChartDataEntry else { continue }
                
                x = e.x
                
                _barShadowRectBuffer.origin.x = CGFloat(x - barWidthHalf)
                _barShadowRectBuffer.size.width = CGFloat(barWidth)
                
                trans.rectValueToPixel(&_barShadowRectBuffer)
                
                if !viewPortHandler.isInBoundsLeft(_barShadowRectBuffer.origin.x + _barShadowRectBuffer.size.width)
                {
                    continue
                }
                
                if !viewPortHandler.isInBoundsRight(_barShadowRectBuffer.origin.x)
                {
                    break
                }
                
                _barShadowRectBuffer.origin.y = viewPortHandler.contentTop
                _barShadowRectBuffer.size.height = viewPortHandler.contentHeight
                
                context.setFillColor(dataSet.barShadowColor.cgColor)
                context.fill(_barShadowRectBuffer)
            }
        }

        let buffer = _buffers[index]
        
        // draw the bar shadow before the values
        if dataProvider.isDrawBarShadowEnabled
        {
            for j in stride(from: 0, to: buffer.rects.count, by: 1)
            {
                
                let barRect = buffer.rects[j]
                
                if (!viewPortHandler.isInBoundsLeft(barRect.origin.x + barRect.size.width))
                {
                    continue
                }
                
                if (!viewPortHandler.isInBoundsRight(barRect.origin.x))
                {
                    break
                }
                
                context.setFillColor(dataSet.barShadowColor.cgColor)
                delegate?.drawBarShadow?(context: context, barRects: buffer.rects, barRect: barRect, index: j, singleColor: dataSet.colors.count == 1)
            }
        }
        
        let isSingleColor = dataSet.colors.count == 1
        var color: UIColor = .clear
        
        if isSingleColor
        {
            color = dataSet.color(atIndex: 0)
            context.setFillColor(color.cgColor)
            
        }

        // In case the chart is stacked, we need to accomodate individual bars within accessibilityOrdereredElements
        let isStacked = dataSet.isStacked
        let stackSize = isStacked ? dataSet.stackSize : 1

        for j in stride(from: 0, to: buffer.rects.count, by: 1)
        {
            let barRect = buffer.rects[j]

            if (!viewPortHandler.isInBoundsLeft(barRect.origin.x + barRect.size.width))
            {
                continue
            }
            
            if (!viewPortHandler.isInBoundsRight(barRect.origin.x))
            {
                break
            }
            
            if !isSingleColor
            {
                // Set the color for the currently drawn value. If the index is out of bounds, reuse colors.
                
                color = dataSet.color(atIndex: j)
               
                context.setFillColor(color.cgColor)

            }
            
            delegate?.drawBar?(context: context, barRects: buffer.rects, barRect: barRect, index: j, color: color, isSingleColor: isSingleColor)
//            context.fill(barRect)
            if drawBorder
            {
                context.setStrokeColor(borderColor.cgColor)
                context.setLineWidth(borderWidth)
                context.stroke(barRect)
            }

//            // Create and append the corresponding accessibility element to accessibilityOrderedElements
//            if let chart = dataProvider as? BarChartView
//            {
//                let element = createAccessibleElement(withIndex: j,
//                                                      container: chart,
//                                                      dataSet: dataSet,
//                                                      dataSetIndex: index,
//                                                      stackSize: stackSize)
//                { (element) in
//                    element.accessibilityFrame = barRect
//                }
//
//                accessibilityOrderedElements[j/stackSize].append(element)
//            }
        }
        
        context.restoreGState()
    }
    
    
    open override func drawHighlighted(context: CGContext, indices: [Highlight])
    {
        guard
            let dataProvider = dataProvider,
            let barData = dataProvider.barData
            else { return }


        context.saveGState()

        var barRect = CGRect()

        for high in indices
        {

            guard
                let set = barData.getDataSetByIndex(high.dataSetIndex) as? IBarChartDataSet,
                set.isHighlightEnabled
                else { continue }

            if let e = set.entryForXValue(high.x, closestToY: high.y) as? BarChartDataEntry
            {
                if !isInBoundsX(entry: e, dataSet: set)
                {
                    continue
                }

                let trans = dataProvider.getTransformer(forAxis: set.axisDependency)

                context.setFillColor(set.highlightColor.cgColor)
                context.setAlpha(set.highlightAlpha)

                let isStack = high.stackIndex >= 0 && e.isStacked

                let y1: Double
                let y2: Double

                if isStack
                {
                    if dataProvider.isHighlightFullBarEnabled
                    {
                        y1 = e.positiveSum
                        y2 = -e.negativeSum
                    }
                    else
                    {
                        let range = e.ranges?[high.stackIndex]

                        y1 = range?.from ?? 0.0
                        y2 = range?.to ?? 0.0
                    }
                }
                else
                {
                    y1 = e.y
                    y2 = 0.0
                }

                print("-- hight --")

                prepareBarHighlight(x: e.x, y1: y1, y2: y2, barWidthHalf: barData.barWidth / 2.0, trans: trans, rect: &barRect)

                setHighlightDrawPos(highlight: high, barRect: barRect)

                delegate?.drawHighlighted?(context: context, barRect: barRect)

            }
        }

        context.restoreGState()
    }
    
    
}

public extension RoundBarchartRenderer {
    
    class Buffer {
        var rects = [CGRect]()
    }
    
}


//MARK: - Draw Data
fileprivate extension RoundBarchartRenderer {
    
    func prepareBuffer(dataSet: IBarChartDataSet, index: Int) {
        guard
            let dataProvider = dataProvider,
            let barData = dataProvider.barData
            else { return }
        
        let barWidthHalf = barData.barWidth / 2.0
    
        let buffer = _buffers[index]
        var bufferIndex = 0
        let containsStacks = dataSet.isStacked
        
        let isInverted = dataProvider.isInverted(axis: dataSet.axisDependency)
        let phaseY = animator.phaseY
        var barRect = CGRect()
        var x: Double
        var y: Double

        
        for i in stride(from: 0, to: min(Int(ceil(Double(dataSet.entryCount) * animator.phaseX)), dataSet.entryCount), by: 1) {
            guard let e = dataSet.entryForIndex(i) as? BarChartDataEntry else { continue }
            
            let vals = e.yValues

            x = e.x
            y = e.y

            if !containsStacks || vals == nil
            {
                let left = CGFloat(x - barWidthHalf)
                let right = CGFloat(x + barWidthHalf)
                var top = isInverted
                    ? (y <= 0.0 ? CGFloat(y) : 0)
                    : (y >= 0.0 ? CGFloat(y) : 0)
                var bottom = isInverted
                    ? (y >= 0.0 ? CGFloat(y) : 0)
                    : (y <= 0.0 ? CGFloat(y) : 0)
                
                var topOffset: CGFloat = 0.0
                var bottomOffset: CGFloat = 0.0
                if let offsetView = dataProvider as? BarChartView
                {
                    let offsetAxis = offsetView.getAxis(dataSet.axisDependency)
                    if y >= 0
                    {
                        // situation 1
                        if offsetAxis.axisMaximum < y
                        {
                            topOffset = CGFloat(y - offsetAxis.axisMaximum)
                        }
                        if offsetAxis.axisMinimum > 0
                        {
                            bottomOffset = CGFloat(offsetAxis.axisMinimum)
                        }
                    }
                    else // y < 0
                    {
                        //situation 2
                        if offsetAxis.axisMaximum < 0
                        {
                            topOffset = CGFloat(offsetAxis.axisMaximum * -1)
                        }
                        if offsetAxis.axisMinimum > y
                        {
                            bottomOffset = CGFloat(offsetAxis.axisMinimum - y)
                        }
                    }
                    if isInverted
                    {
                        // situation 3 and 4
                        // exchange topOffset/bottomOffset based on 1 and 2
                        // see diagram above
                        (topOffset, bottomOffset) = (bottomOffset, topOffset)
                    }
                }
                //apply offset
                top = isInverted ? top + topOffset : top - topOffset
                bottom = isInverted ? bottom - bottomOffset : bottom + bottomOffset

                // multiply the height of the rect with the phase
                // explicitly add 0 + topOffset to indicate this is changed after adding accessibility support (#3650, #3520)
                if top > 0 + topOffset
                {
                    top *= CGFloat(phaseY)
                }
                else
                {
                    bottom *= CGFloat(phaseY)
                }

                barRect.origin.x = left
                barRect.origin.y = top
                barRect.size.width = right - left
                barRect.size.height = bottom - top
                buffer.rects[bufferIndex] = barRect
                bufferIndex += 1
            }
            else
            {
                var posY = 0.0
                var negY = -e.negativeSum
                var yStart = 0.0
                
                // fill the stack
                for k in 0 ..< vals!.count
                {
                    let value = vals![k]
                    
                    if value == 0.0 && (posY == 0.0 || negY == 0.0)
                    {
                        // Take care of the situation of a 0.0 value, which overlaps a non-zero bar
                        y = value
                        yStart = y
                    }
                    else if value >= 0.0
                    {
                        y = posY
                        yStart = posY + value
                        posY = yStart
                    }
                    else
                    {
                        y = negY
                        yStart = negY + abs(value)
                        negY += abs(value)
                    }
                    
                    let left = CGFloat(x - barWidthHalf)
                    let right = CGFloat(x + barWidthHalf)
                    var top = isInverted
                        ? (y <= yStart ? CGFloat(y) : CGFloat(yStart))
                        : (y >= yStart ? CGFloat(y) : CGFloat(yStart))
                    var bottom = isInverted
                        ? (y >= yStart ? CGFloat(y) : CGFloat(yStart))
                        : (y <= yStart ? CGFloat(y) : CGFloat(yStart))
                    
                    // multiply the height of the rect with the phase
                    top *= CGFloat(phaseY)
                    bottom *= CGFloat(phaseY)
                    
                    barRect.origin.x = left
                    barRect.size.width = right - left
                    barRect.origin.y = top
                    barRect.size.height = bottom - top
                    
                    buffer.rects[bufferIndex] = barRect
                    bufferIndex += 1
                }
            }
        }
    }
    
    func createAccessibleElement(withIndex idx: Int,
                                          container: BarChartView,
                                          dataSet: IBarChartDataSet,
                                          dataSetIndex: Int,
                                          stackSize: Int,
                                          modifier: (NSUIAccessibilityElement) -> ()) -> NSUIAccessibilityElement {
        let element = NSUIAccessibilityElement(accessibilityContainer: container)
        let xAxis = container.xAxis

        guard let e = dataSet.entryForIndex(idx/stackSize) as? BarChartDataEntry else { return element }
        guard let dataProvider = dataProvider else { return element }

        // NOTE: The formatter can cause issues when the x-axis labels are consecutive ints.
        // i.e. due to the Double conversion, if there are more than one data set that are grouped,
        // there is the possibility of some labels being rounded up. A floor() might fix this, but seems to be a brute force solution.
        let label = xAxis.valueFormatter?.stringForValue(e.x, axis: xAxis) ?? "\(e.x)"

        var elementValueText = dataSet.valueFormatter?.stringForValue(
            e.y,
            entry: e,
            dataSetIndex: dataSetIndex,
            viewPortHandler: viewPortHandler) ?? "\(e.y)"

        if dataSet.isStacked, let vals = e.yValues
        {
            let labelCount = min(dataSet.colors.count, stackSize)

            let stackLabel: String?
            if (dataSet.stackLabels.count > 0 && labelCount > 0) {
                let labelIndex = idx % labelCount
                stackLabel = dataSet.stackLabels.indices.contains(labelIndex) ? dataSet.stackLabels[labelIndex] : nil
            } else {
                stackLabel = nil
            }
            
            //Handles empty array of yValues
            let yValue = vals.isEmpty ? 0.0 : vals[idx % vals.count]
            
            elementValueText = dataSet.valueFormatter?.stringForValue(
                yValue,
                entry: e,
                dataSetIndex: dataSetIndex,
                viewPortHandler: viewPortHandler) ?? "\(e.y)"

            if let stackLabel = stackLabel {
                elementValueText = stackLabel + " \(elementValueText)"
            } else {
                elementValueText = "\(elementValueText)"
            }
        }

        let dataSetCount = dataProvider.barData?.dataSetCount ?? -1
        let doesContainMultipleDataSets = dataSetCount > 1

        element.accessibilityLabel = "\(doesContainMultipleDataSets ? (dataSet.label ?? "")  + ", " : "") \(label): \(elementValueText)"

        modifier(element)

        return element
    }
    
    func accessibilityCreateEmptyOrderedElements() -> [[NSUIAccessibilityElement]] {
        guard let chart = dataProvider as? BarChartView else { return [] }

        // Unlike Bubble & Line charts, here we use the maximum entry count to account for stacked bars
        let maxEntryCount = chart.data?.maxEntryCountSet?.entryCount ?? 0

        return Array(repeating: [NSUIAccessibilityElement](),
                     count: maxEntryCount)
    }
    
    
    
}

//MARK: - Draw Highlighted
fileprivate extension RoundBarchartRenderer {
    
    func isInBoundsX(entry e: ChartDataEntry, dataSet: IBarLineScatterCandleBubbleChartDataSet) -> Bool {
        let entryIndex = dataSet.entryIndex(entry: e)
        return Double(entryIndex) < Double(dataSet.entryCount) * animator.phaseX
    }
    
     func setHighlightDrawPos(highlight high: Highlight, barRect: CGRect)
    {
        high.setDraw(x: barRect.midX, y: barRect.origin.y)
    }
    
    
    
}


extension RoundBarchartRenderer {
    
    public enum RoundRectCorner {
        
        // 没有圆角
        case none
        
        // 上面倒角
        case top
        
        // 下面倒角
        case bottom
        
        // 上下倒角
        case topAndBottom
        
        // 每一个entry 上下都倒角
        case eachEntryTopAndBottom
        
    }
    
    /// 倒角以宽高谁为准
    public enum RadiuDirection {
        
        case width
        case height
        
    }
    
}



